﻿using Microsoft.Office.Server.Search.Administration;
using Microsoft.Office.Server.Search.Administration.Query;
using Microsoft.Office.Server.Search.Query.Rules;
using SPSDeploy.Extensions.Controllers;
using SPSDeploy.Extensions.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

namespace SPSDeploy.Extensions.Modules.Update
{
    /// <summary>
    /// Search proxy update module
    /// </summary>
    internal class SearchProxyModule : SearchModule
    {

        /// <summary>
        /// Initialization
        /// </summary>
        /// <param name="controller"></param>
        internal SearchProxyModule(BaseController controller)
            : base(controller)
        {
        }

        /// <summary>
        /// Execute module
        /// </summary>
        /// <param name="application"></param>
        internal override void Execute(XElement application)
        {
            SearchServiceApplicationProxy serviceApplicationProxy = (SearchServiceApplicationProxy)ServiceApplicationProxyExtension.GetServiceApplicationProxy(application.Attribute("name").Value, application.Attribute("type").Value);
            if (serviceApplicationProxy != null)
            {
                FederationManager manager = new FederationManager(serviceApplicationProxy);
                UpdateSearchApplicationProxy(application, serviceApplicationProxy);
                UpdateResultSources(application, manager, "searchServiceApplicationProxy");
                UpdateResultTypes(application, manager, new ResultItemTypeManager(serviceApplicationProxy), "searchServiceApplicationProxy");
                UpdateQueryRules(application, manager, new QueryRuleManager(serviceApplicationProxy), "searchServiceApplicationProxy");
                UpdateManagedProperties(application, serviceApplicationProxy);
                controller.OnProgressMessage(string.Format("Search service application proxy '{0}' information updated.", application.Attribute("name").Value));
            }
        }

        #region Search Application Proxy
        /// <summary>
        /// Update search application proxy
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="proxy"></param>
        private void UpdateSearchApplicationProxy(XElement parent, SearchServiceApplicationProxy proxy)
        {
            bool proxyUpdated = false;
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplicationProxy/updates/update"))
                switch (element.Attribute("value").Value)
                {
                    case "mergeWithDefaultPartition":
                        proxy.MergeWithDefaultPartition = Convert.ToBoolean(element.Attribute("to").Value);
                        proxyUpdated = true;
                        break;
                }
            if (proxyUpdated)
                proxy.Update();
        }
        #endregion

        #region Managed Properties
        /// <summary>
        /// Update managed properties
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="proxy"></param>
        private void UpdateManagedProperties(XElement parent, SearchServiceApplicationProxy proxy)
        {
            List<SearchObjectOwner> owners = new List<SearchObjectOwner>();
            QueryRuleManager manager = new QueryRuleManager(proxy);
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplicationProxy/managedProperties/deletes/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                proxy.DeleteManagedProperty(proxy.GetManagedProperties(null, int.MaxValue, owner).Where(i => i.Name == element.Attribute("name").Value).First(), owner);
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplicationProxy/managedProperties/adds/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                ManagedPropertyInfo item = proxy.CreateManagedProperty(element.Attribute("name").Value, (ManagedDataType)Enum.Parse(typeof(ManagedDataType), element.Attribute("type").Value), owner);
                UpdateManagedPropertyCreate(element, item);
                proxy.UpdateManagedProperty(item, owner);
                UpdateManagedPropertyAliases(item, element.Descendants("aliases").FirstOrDefault(), proxy, owner);
                UpdateManagedPropertyMappedCrawledProperties(item, element.Descendants("crawledProperties").FirstOrDefault(), proxy, owner);
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplicationProxy/managedProperties/updates/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                ManagedPropertyInfo item = proxy.GetManagedProperty(element.Attribute("name").Value, owner);
                UpdateManagedPropertyUpdate(element, item);
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "aliases":
                            UpdateManagedPropertyAliases(item, element.Descendants("aliases").FirstOrDefault(), proxy, owner);
                            break;
                        case "crawledProperties":
                            UpdateManagedPropertyMappedCrawledProperties(item, element.Descendants("crawledProperties").FirstOrDefault(), proxy, owner);
                            break;
                    }
                proxy.UpdateManagedProperty(item, owner);
            }
        }

        /// <summary>
        /// Update managed property aliases
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="proxy"></param>
        /// <param name="owner"></param>
        private void UpdateManagedPropertyAliases(ManagedPropertyInfo item, XElement parent, SearchServiceApplicationProxy proxy, SearchObjectOwner owner)
        {
            List<string> aliases = new List<string>();
            if (parent != null)
                foreach (XElement element in parent.Descendants("alias"))
                    aliases.Add(element.Value);
            proxy.SetManagedPropertyAliases(item, aliases.ToArray(), owner);
        }

        /// <summary>
        /// Update managed property mapped crawled properties
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="proxy"></param>
        /// <param name="owner"></param>
        private void UpdateManagedPropertyMappedCrawledProperties(ManagedPropertyInfo item, XElement parent, SearchServiceApplicationProxy proxy, SearchObjectOwner owner)
        {
            int order = 1;
            List<MappingInfo> mappings = new List<MappingInfo>();
            if (parent != null)
                foreach (XElement element in parent.Descendants("crawledProperty"))
                {
                    CrawledPropertyInfo crawledProperty = proxy.GetAllCrawledProperties(element.Attribute("name").Value, element.Attribute("category").Value, 0, owner).FirstOrDefault();
                    if (crawledProperty != null)
                        mappings.Add(new MappingInfo() { CrawledPropertyName = crawledProperty.Name, CrawledPropset = crawledProperty.Propset, SchemaId = crawledProperty.SchemaId, MappingOrder = order++, ManagedPid = item.Pid });
                    else
                        controller.OnProgressMessage(MessageType.Warning, string.Format("Crawled property '{0}:{1}' is not available. Mapping managed property skipped.", element.Attribute("name").Value, element.Attribute("category").Value));
                }
            proxy.SetManagedPropertyMappings(item, mappings.ToArray(), owner);
        }
        #endregion

    }
}